package scau.markmapscau.Model.Node;

import javafx.scene.paint.Color;
import scau.markmapscau.Model.Enum.ArrangeMode;

import java.io.Serializable;
import java.util.*;

public class NodeArranger implements NodeUtilInterface,Serializable {
    private LinkedHashMap<Integer,Node> nodeList;//存储所有节点列表,主数据结构
    private HashMap<Integer,LinkedList<Integer> > LayerList;

    private double minHeight = 3000;//initVal
    private double minWidth  = 3000;

    private int allocId;//分配节点用的ID

    private double pointerX;
    private double pointerY;

    private ArrayList<Double> scaleList;
    private double scale;
    private int maxdepth=-1;

    private String color;
    private String style;
    private String backColor;

    private ArrangeMode arrangeMode;

    public static double WIDTH = 200;
    public static double HEIGHT = 50;
    public static double X_BIAS = 100;//X之间的间距
    public static double Y_BIAS_NODE =20;

    public NodeArranger(){
        allocId = 1;//为子节点分配的id
        nodeList = new LinkedHashMap<>();//初始化节点列表
        LayerList = new HashMap<>();
        scaleList = new ArrayList<>();
        scale=1;
        backColor = "#ffffff";
//        backColor = new Color(255/255f,255/255f,255/255f,1);
        arrangeMode = ArrangeMode.RIGHT;
    }

    /**
     * @brief 注意:由于子节点可能还有子节点,所以这就是一个递归的问题,需要使用递归来解决,因此编写了递归删除函数
     * TODO:采用原生递归模式,可以用stack改写
     * @param id 将要删除的节点id
     * @return
     */
    @Override
    public void deleteNode(int id) {
        Node parentNode = this.getNodeList().get(this.getNodeList().get(id).getParentId());
        if(parentNode == null){
            return ;
        }
        LinkedList<Integer> childrenIdList = parentNode.getSonNodeList();//删除父节点记录
        for(Iterator<Integer> iterator =childrenIdList.iterator();iterator.hasNext();){
            int sonId = iterator.next();
            if(sonId == id){
                iterator.remove();
                break;
            }
        }

        if (childrenIdList.size() == 0) {
            nodeList.remove(id);
            return;
        }
        for(Iterator<Integer> iterator = this.nodeList.get(id).getSonNodeList().iterator();iterator.hasNext();){
            Integer sonId = iterator.next();
            releaseNode(sonId);
        }
        nodeList.remove(id);
    }

    public void releaseNode(Integer id) {
        if (this.nodeList.get(id).getSonNodeList().size() == 0) {
            nodeList.remove(id);
            return;
        }
        for(Iterator<Integer> iterator = this.nodeList.get(id).getSonNodeList().iterator();iterator.hasNext();){
            releaseNode(iterator.next());
        }
        nodeList.remove(id);
    }

    @Override
    public void newNode(int parentId) {
        //1.获取父亲节点及其信息
        Node parentNode = nodeList.get(parentId);
        //2.设置子节点的主键,主键自增
        int sonId = this.allocId;this.allocId++;
        //3.计算子节点的高度和宽度
        maxdepth = Math.max(maxdepth,parentNode.getDepth()+1);
        setScaleList();
        double nodeWidth = getNodeWidth(parentNode.getDepth()+1);
        double nodeHeight = getNodeHeight(parentNode.getDepth()+1);
        //4.通过当前信息新建子节点
        Node newNode = new Node(sonId,nodeWidth,nodeHeight,"#ffffff","FangSong");//自增主键
        //5.将节点与父节点连接上
        newNode.setDepth(parentNode.getDepth()+1);//增加深度,为父节点深度+1
        newNode.setParentId(parentNode.getId());
        newNode.setSize(parentNode.getSize()-1);
        parentNode.getSonNodeList().add(sonId);//将子节点id加入到父节点的链表中去
        nodeList.put(sonId,newNode);//将节点加入节点总链表中
    }

    @Override
    public void setParentNode(Node parentNode) {
        //当该节点为中心节点时,设其节点id为0的
        //直接插入到节点列表即可
        parentNode.setParentId(-1);//设置父节点的id为-1,表示没有上级了
        parentNode.setDepth(0);//设置深度为0
        nodeList.put(parentNode.getId(),parentNode);//将父节点插入节点总链表
    }

    @Override
    public LinkedHashMap<Integer, Node> getNodeList() {return nodeList;}
    public void setNodeList(LinkedHashMap<Integer,Node> nodeList){
        this.nodeList=nodeList;
    }

    public double getNodeWidth(int depth){return WIDTH*scaleList.get(depth)*scale;}

    public double getNodeHeight(int depth){return HEIGHT*scaleList.get(depth)*scale;}

    public double getMinHeight(){return this.minHeight;}

    public double getMinWidth(){return this.minWidth;}

    public void setArrangeMode(ArrangeMode arrangeMode) {this.arrangeMode = arrangeMode;}
    public ArrangeMode getArrangeMode(){return this.arrangeMode;}

    public void setScale(double scale){this.scale = scale;}

    public double getScale(){return this.scale;}

    public void setColor(String color){this.color = color;}
    public String getColor(){return this.color;}

    public void setStyle(String style) {this.style = style;}
    public String getStyle(){return this.style;}

    public void setBackColor(String backColor){
        this.backColor = backColor;
    }
    public String getBackColor(){
        return this.backColor;
    }

    /*以下是计算布局排布的代码*/
    /*首先讲讲思路以及使用代码布局的步骤*/
    //思路:
    //第一:为了使得滑动窗口的大小自动适应节点的个数,首先需要根据当前的节点列表计算出每一层的最大高度以及最大宽度
    //将这个最大高度和最大宽度作为“底线”来设置滑动窗口的大小,这样就可以保证不会超出滑动窗口的大小

    //第二:关于排布策略,为了维护树状结构,先从父节点开始,深度优先搜索,一直画到这个分支的最后一个节点上去
    //那么如何来确定排布时,每个节点所在的位置呢?我们可以将思维导图分块,父节点的所有子节点就是一块,这一句块应当是独立的，不与其他节点重合的
    //也就是说,当前父节点和它的所有子节点,孙子节点......都应当占有一个完整的方块

    //第三:计算布局,也即计算各节点的XY值,这里以右侧布局为例进行分析,左侧布局首先要求的是中心节点位于最左侧(X轴选定初始点为50)
    //然后中心节点的Y坐标应该是在其块高的一半的地方,中心节点布局结束后,就为它的子节点进行布局
    //首先第一个要解决的是关于X轴的偏移,由于是从左侧一直排,排到最右边的
    //因此子节点的X轴的计算公式是父亲节点的X轴坐标+X轴的width+X_bias
    //    子节点的Y轴的计算公式是子节点的块宽+Y_bias

    //第四:细节问题,由于javafx的sceneBuilder中规定的大小是不会变的,因此可能出现越界问题(也就是不够大的问题)
    //因此在画图的过程中,需要计算出需要容纳的高度和宽度,然后以此作为一个最小的高度和宽度赋值给scrollPane
    //TODO:同时,由于画图是实时变化的,因此需要做一个聚焦功能

    public void setDrawingPointer(double pointerX,double pointerY){//初始化画图的XY指针,辅助方法
        this.pointerX = pointerX;
        this.pointerY = pointerY;
    }

    public void setMinHeightWidth(double minHeight,double minWidth){
        this.minHeight = Math.max(minHeight,this.minHeight);
        this.minWidth = Math.max(minWidth,this.minWidth);
    }

    public void getMaxDepth(){
        for(Node node:nodeList.values()){
            maxdepth = Math.max(maxdepth,node.getDepth());
        }
    }


    public void setScaleList(){
        this.getMaxDepth();
        scaleList.clear();
        scaleList.add(1.0);
        for(int i=1;i<=maxdepth;i++){
            scaleList.add(scaleList.get(i-1)*0.90);
        }
    }


    //实现排布功能步骤:
    //一、初始化每一个思维导图节点块
    public void initNodeQueue(){
        //初始化目标:初始化块宽、块高、计数变量、
        //遍历节点列表,初始化节点内容
        LayerList.clear();
        maxdepth = -1;
        //System.out.println(LayerList);
        for(Node node:this.nodeList.values()){
            maxdepth = Math.max(node.getDepth(),maxdepth);
            LinkedList<Integer> list  = LayerList.get(node.getDepth());
            if(LayerList.get(node.getDepth()) == null){
                list = new LinkedList<>();
                LayerList.put(node.getDepth(),list);
            }
            list.add(node.getId());//建立层级节点
            //从最后一层节点出发
            node.setNodeHeight(this.getNodeHeight(node.getDepth()));
            node.setNodeWidth(this.getNodeWidth(node.getDepth()));
            node.setNodeBlockWidth(node.getNodeWidth());
            node.setNodeBlockHeight(node.getNodeHeight());
        }
        /*
        for(Node node:this.getNodeList().values()){
            node.setIsDrawedAllChildren(false);
            //同时需要找出思维导图的叶子节点,该节点为开始划分块宽的起点
            //System.out.println(node.getSonNodeList().size());
            if(node.getSonNodeList().size() == 0){//当没有子节点的时候,那么就认为是叶子节点
                //叶子节点入队
                node.setNodeBlockHeight(getNodeHeight(node.getDepth()));//初始化块高
                node.setNodeBlockWidth(getNodeWidth(node.getDepth()));//初始化块宽
                drawingQueue.offer(node);
            }else{
                node.setNodeBlockHeight(0);//初始化块高
                node.setNodeBlockWidth(0);//初始化块宽
            }
        }
        */
    }

    //二、计算思维导图块
    public void calculatesBlocks(){
        /*
        //注意,由于可能存在多个叶子节点对应于同一个父亲节点,因此需要对这个父亲节点进行标记
        while(!drawingQueue.isEmpty()){
            //1.首先将队中的叶子节点(或者是后续的处于正确位置的节点)出队
            Node node = drawingQueue.poll();
            //System.out.println(node);
            //2.由叶子节点去计算这一个叶子节点,首先通过叶子节点查找父亲节点
            int parentID = node.getParentId();
            if(parentID == -1){
                this.setMinHeightWidth(node.getNodeBlockHeight(),node.getNodeBlockWidth());
                continue;
            }
            Node parentNode = this.getNodeList().get(parentID);
            if(parentNode.isDrawedAllChildren()){continue;}//如果当前的父节点的子节点已经全部画完了,那么检查下一个节点
            //3.再得到父亲节点的所有子节点,计算这个块宽应该是多少
            LinkedList<Integer> sonNodeList = parentNode.getSonNodeList();
            //4.遍历子节点链表
            double maxChildWidth = -1;
            for(Integer id:sonNodeList) {
                int sonId =id;
                Node sonNode = nodeList.get(sonId);
                double nodeBlockHeight = sonNode.getNodeBlockHeight();//当前节点的块高度
                parentNode.setNodeBlockHeight(parentNode.getNodeBlockHeight() + nodeBlockHeight + Y_BIAS_NDOE);//加入到块高
                maxChildWidth = Math.max(maxChildWidth, sonNode.getNodeWidth() + X_BIAS);
            }
            //5.最后将父节点加入到当前搜索队列中,再搜索父节点的父节点
            parentNode.setIsDrawedAllChildren(true);
            parentNode.setNodeBlockWidth(maxChildWidth);
            drawingQueue.offer(parentNode);

        }
        */
        for(int i = maxdepth;i>=0;i--) {
            LinkedList<Integer> list = LayerList.get(i);
            if(list.size() == 0){
                continue;
            }
            for (Integer id : list) {
                Node node = this.getNodeList().get(id);
                if(node.getParentId() == -1){
                    this.setMinHeightWidth(node.getNodeBlockHeight(),node.getNodeBlockWidth());
                    break;
                }
                Node parentNode = this.getNodeList().get(node.getParentId());
                parentNode.setNodeBlockHeight(parentNode.getNodeBlockHeight() + node.getNodeBlockHeight()+Y_BIAS_NODE);
            }
        }
    }

    private void getNodeXY(int now_id, int flag) {
        Node nowNode = this.getNodeList().get(now_id);
        if(nowNode == null){
            return ;
        }
        if (nowNode.getSonNodeList().size() == 0) {
            return;
        }
        if (flag == 0) {//中心布局要特殊处理
            int half = (nowNode.getSonNodeList().size() + 1) / 2;//加一是为了将编译器向下取整变成向上取整
            int leftSize = 0;
            int rightSize = 0;

            for (int childId : nowNode.getSonNodeList()) {
                Node childNode = this.getNodeList().get(childId);
                if (nowNode.getCounter() < half) {
                    rightSize += childNode.getNodeBlockHeight();
                } else {
                    leftSize += childNode.getNodeBlockHeight();
                }
                //计数
                nowNode.setCounter(nowNode.getCounter() + 1);
            }
            rightSize /= 2;
            leftSize /= 2;

            int yPointer = (int)nowNode.getNodeBlockHeight() / 2;
            Double nowY = nowNode.getNodeLocationY();

            nowNode.setCounter(0);
            for (Integer childId : nowNode.getSonNodeList()) {
                Node childNode = this.getNodeList().get(childId);
                if (nowNode.getCounter() < half) {//前一半右树  // 小于,和上面保持一致
                    flag = 1;//右树
                    yPointer -= childNode.getNodeBlockHeight() / 2+Y_BIAS_NODE;
                    double y = nowY - yPointer + leftSize;//还要计算偏移
                    childNode.setNodeLocationY(y);
                    yPointer -= childNode.getNodeBlockHeight() / 2+Y_BIAS_NODE;
                } else {
                    flag = -1;//左树
                    yPointer -= childNode.getNodeBlockHeight() / 2+Y_BIAS_NODE;
                    double y = nowY - yPointer - rightSize;//还要计算偏移
                    childNode.setNodeLocationY(y);
                    yPointer -= childNode.getNodeBlockHeight() / 2+Y_BIAS_NODE;
                }
                double baseX = nowNode.getNodeLocationX() + flag * (nowNode.getNodeWidth() + X_BIAS);  //x轴 通过flag实现:左树累加、右树累减
                childNode.setNodeLocationX(baseX);
                //计数
                nowNode.setCounter(nowNode.getCounter() + 1);
                getNodeXY(childId, flag);
            }
        }else{
            double baseX = nowNode.getNodeLocationX() + flag * (nowNode.getNodeBlockWidth() + X_BIAS);
            int yPointer = (int)(nowNode.getNodeBlockHeight() / 2);
            double nowY = nowNode.getNodeLocationY();

            for (int childId : nowNode.getSonNodeList()) {
                if(nowNode.getSonNodeList().size() == 1){
                    Node childNode = this.getNodeList().get(childId);
                    childNode.setNodeLocationX(baseX);
                    childNode.setNodeLocationY(nowY);
                    getNodeXY(childId, flag);
                }else{
                    Node childNode = this.getNodeList().get(childId);
                    childNode.setNodeLocationX(baseX);
                    yPointer -= (int)childNode.getNodeBlockHeight() / 2+Y_BIAS_NODE;
                    double y = nowY - yPointer;
                    childNode.setNodeLocationY(y);
                    yPointer -= (int)childNode.getNodeBlockHeight() / 2+Y_BIAS_NODE;
                    getNodeXY(childId, flag);
                }
            }
        }
    }

    /*集成以上方法,封装为接口供调用*/
    public void arrangeLayout(){
        setScaleList();
        initNodeQueue();
        calculatesBlocks();
        Node root = this.getNodeList().get(0);
        switch (this.arrangeMode){
            case RIGHT -> {root.setNodeLocationX(200);root.setNodeLocationY((int)this.getMinHeight()/2);getNodeXY(root.getId(),1);break;}
            case LEFT ->  {root.setNodeLocationX((int)this.getMinWidth()-900);root.setNodeLocationY((int)this.getMinHeight()/2);getNodeXY(root.getId(),-1);break;}
            case AUTO ->  {root.setNodeLocationX((int)this.getMinWidth()/2);root.setNodeLocationY((int)this.getMinHeight()/2);getNodeXY(root.getId(),0);break;}
            default -> {break;}
        }
        return ;
    }

    public void deBug(){
        for(Node node:this.getNodeList().values()){
            System.out.print("id:"+node.getId()+" ");
        }
        System.out.println();
    }
    public double[] getBoarder(){
        double[] boarder = new double[4];
        double leftX=minWidth,rightX=0,topY=minHeight,bottomY=0;
        for(Node node :this.getNodeList().values()){
            leftX = Math.min(leftX,node.getNodeLocationX());
            rightX = Math.max(rightX,node.getNodeLocationX()+node.getNodeWidth());
            topY = Math.min(topY,node.getNodeLocationY());
            bottomY = Math.max(bottomY,node.getNodeLocationY()+node.getNodeHeight());
        }
        boarder[0]=leftX-100;boarder[1]=topY-100; //往左上方移了一点点
        boarder[2]=rightX;boarder[3]=bottomY;
        return boarder;
    }

    public void enlargeFontSize(boolean isMax,boolean isMin){
        if(isMax || isMin){
            return ;
        }
        for(Node node:this.getNodeList().values()){
            node.setSize(node.getSize()+1);
        }
    }

    public void reduceFontSize(boolean isMax,boolean isMin){
        if(isMax || isMin){
            return ;
        }
        for(Node node:this.getNodeList().values()){
            node.setSize(node.getSize()-1);
        }
    }

    //三、通过思维导图块宽来计算出具体的坐标以及节点大小
    //waring:BUG here
    /*
    public void calculatesLocationRight(){
        //使用的是广度优先搜索,BFS
        //首先规定起始的位置,为根节点赋值
        LinkedBlockingQueue nodeQueue = new LinkedBlockingQueue<>();
        nodeQueue.offer(0);
        //左侧布局,X轴的起始点应当是200,Y轴的起点应当是根节点的
        this.setDrawingPointer(200,minHeight/2);
        Node root =this.getNodeList().get(0);
        root.setNodeLocationX(this.pointerX);
        root.setNodeLocationY(this.pointerY);
        while(!nodeQueue.isEmpty()){//如果搜索没有结束
            int parentId = (Integer) nodeQueue.poll();
            Node parentNode = this.getNodeList().get(parentId);
            double baseYHigh = parentNode.getNodeLocationY();//上方指针
            double baseYLow = parentNode.getNodeLocationY();//下方指针
            double baseX = parentNode.getNodeLocationX()+parentNode.getNodeBlockWidth()+X_BIAS;
            int count =0;
            System.out.println(parentId+" :"+parentNode.getSonNodeList());
            for(Integer id:parentNode.getSonNodeList()){
                Node nodeNow = this.getNodeList().get(id);
                double locationY = 0;
                if(count == 0){
                    //1.当画第一个节点的时候,此时该节点与父节点同高
                    locationY = baseYHigh;//任意一个指针都行
                    //2.然后对指针进行初始化
                    //高指针移动,移动的方式是:从当前的Y位置向上移动当前节点的块高的一半
                    baseYHigh = baseYHigh - nodeNow.getNodeBlockHeight() - Y_BIAS_NDOE;
                    //低指针移动,移动的方式是,从当前Y位置向下移动当前节点的块高的一半,再加上当前节点的自身高度
                    baseYLow = baseYLow + nodeNow.getNodeBlockHeight() +Y_BIAS_NDOE;
                    //指针初始化完毕,此时高指针指向了当前节点的块的左上方,低指指向了当前节点的左下方
                }else if(count %2 == 1){//该节点总是先画,画高节点,高指针
                    locationY  = baseYHigh -nodeNow.getNodeBlockHeight()/2 ;
                    baseYHigh = baseYHigh - nodeNow.getNodeBlockHeight() - Y_BIAS_NDOE;
                    System.out.println("drawing id:"+id+" "+baseYHigh+" "+count);
                }else if(count %2 == 0){
                    locationY = baseYLow +nodeNow.getNodeBlockHeight()/2 ;
                    baseYLow = baseYLow + nodeNow.getNodeBlockHeight()+Y_BIAS_NDOE;
                }
                count++;
                nodeNow.setNodeLocationY(locationY);nodeNow.setNodeLocationX(baseX);
                nodeQueue.offer(nodeNow.getId());
            }
        }
    }

    public void calculatesLocationLeft(){

    }

    public void calculatesLocationAuto(){

    }
    */


}
